use crate::ast::*;
use crate::error::{CompilerError, Result};
use crate::llvm_backend::LLVMBackend;
use inkwell::types::BasicTypeEnum;
use inkwell::values::BasicValueEnum;

impl<'ctx> LLVMBackend<'ctx> {


    /// Generate take() method - takes the value out, leaving None
    /// Returns the original Option value
    pub(super) fn generate_option_take(
        &mut self,
        option_value: BasicValueEnum<'ctx>,
        option_type: &crate::ast::types::OptionType,
        _location: &crate::error::Location,
        var_name: Option<&str>,
    ) -> Result<BasicValueEnum<'ctx>> {
        // Get the variable pointer for mutation
        let var_ptr = if let Some(name) = var_name {
            self.variable_map
                .get(name)
                .ok_or_else(|| CompilerError::internal(&format!("Undefined variable: {}", name)))?
                .into_pointer_value()
        } else {
            return Err(CompilerError::internal(
                "take() requires a variable (cannot be used on temporary values)",
            ));
        };

        // Get the Option LLVM type
        let option_llvm_type = match self.get_option_llvm_type(option_type)? {
            BasicTypeEnum::StructType(st) => st,
            _ => return Err(CompilerError::internal("Option type must be a struct")),
        };

        // Extract tag to check if Some
        let tag = self.extract_option_tag(option_value)?;
        let zero = self.context.i32_type().const_int(0, false);
        let is_some =
            self.builder
                .build_int_compare(inkwell::IntPredicate::EQ, tag, zero, "take_check")?;

        let current_fn = self
            .current_function
            .ok_or_else(|| CompilerError::internal("Cannot create blocks outside function"))?;

        let some_block = self.context.append_basic_block(current_fn, "take_some");
        let none_block = self.context.append_basic_block(current_fn, "take_none");
        let continue_block = self.context.append_basic_block(current_fn, "take_continue");

        // Allocate result Option
        let result_alloca = self.builder.build_alloca(option_llvm_type, "take_result")?;

        self.builder
            .build_conditional_branch(is_some, some_block, none_block)?;

        // Some block: save current value, set to None
        self.builder.position_at_end(some_block);
        // Store the original Option value (which contains Some) in result
        self.builder.build_store(result_alloca, option_value)?;
        // Set the variable to None
        let none_value = self.create_option_value(1, None, option_type)?;
        self.builder.build_store(var_ptr, none_value)?;
        self.builder.build_unconditional_branch(continue_block)?;

        // None block: return None
        self.builder.position_at_end(none_block);
        let none_result = self.create_option_value(1, None, option_type)?;
        self.builder.build_store(result_alloca, none_result)?;
        self.builder.build_unconditional_branch(continue_block)?;

        // Continue block: return the original Option value
        self.builder.position_at_end(continue_block);
        let result = self
            .builder
            .build_load(option_llvm_type, result_alloca, "take_value")?;
        Ok(result)
    }


    /// Generate replace() method - replaces the value, returns the old Option
    /// Returns the original Option value
    pub(super) fn generate_option_replace(
        &mut self,
        option_value: BasicValueEnum<'ctx>,
        option_type: &crate::ast::types::OptionType,
        arguments: &[Expression],
        location: &crate::error::Location,
        var_name: Option<&str>,
    ) -> Result<BasicValueEnum<'ctx>> {
        if arguments.len() != 1 {
            return Err(CompilerError::type_error(
                location.line,
                location.column,
                &format!(
                    "Option.replace() expects 1 argument (new value), got {}",
                    arguments.len()
                ),
            ));
        }

        // Get the variable pointer for mutation
        let var_ptr = if let Some(name) = var_name {
            self.variable_map
                .get(name)
                .ok_or_else(|| CompilerError::internal(&format!("Undefined variable: {}", name)))?
                .into_pointer_value()
        } else {
            return Err(CompilerError::internal(
                "replace() requires a variable (cannot be used on temporary values)",
            ));
        };

        // Generate the new value
        let new_value = self.generate_expression(&arguments[0])?;

        // Get the Option LLVM type
        let option_llvm_type = match self.get_option_llvm_type(option_type)? {
            BasicTypeEnum::StructType(st) => st,
            _ => return Err(CompilerError::internal("Option type must be a struct")),
        };

        // Save the original Option value
        let result_alloca = self
            .builder
            .build_alloca(option_llvm_type, "replace_result")?;
        self.builder.build_store(result_alloca, option_value)?;

        // Create new Option::Some with the new value
        let new_option = self.create_option_value(0, Some(new_value), option_type)?;

        // Store the new Option value in the variable
        self.builder.build_store(var_ptr, new_option)?;

        // Return the original Option value
        let result = self
            .builder
            .build_load(option_llvm_type, result_alloca, "replace_value")?;
        Ok(result)
    }

}
